; (function (global, $, window, undefined) {

    // Lets run in ECMAScript 5 Strict Mode to
    // weed out unsafe operations and potential bugs
    "use strict";
;/* =============================================================
* codagenicJS Library
* http://www.codagenic.com/
* =============================================================
* Copyright 2012 Codagenic, All Rights Reserved.
*
* Created by Byron Salau
* ============================================================ */



	// Our library       
	if ('undefined' === typeof Codagenic)
	{
		var Codagenic = {};

		if ('undefined' !== typeof window)
		{
			window.Cg = window.Codagenic = Codagenic;
		}
	}

	//Extend jQuery
	if ('undefined' === typeof $.codagenic)
	{
		$.codagenic = {};
	}

	// some references
	var undef,
		DOC = window.document,
		NAV = window.navigator.userAgent.toLowerCase(),
		HASH = window.location.hash.replace(/#\//, ''),
		$DOC   = $( DOC ),
		$WIN = $(window),
		ISTOUCH = !!('ontouchstart' in window),
		ISIFRAME = (window.location !== window.parent.location) ? true : false,

		// constants
		VERSION = "@VERSION",
		DEBUG = true,
		F = function(){},
		NULL_TYPE = 'Null',
		UNDEFINED_TYPE = 'Undefined',
		BOOLEAN_TYPE = 'Boolean',
		NUMBER_TYPE = 'Number',
		STRING_TYPE = 'String',
		OBJECT_TYPE = 'Object',
		ERROR_TYPE = 'Error',
		FUNCTION_TYPE = 'Function',
		FUNCTION_CLASS = '[object Function]',
		BOOLEAN_CLASS = '[object Boolean]',
		NUMBER_CLASS = '[object Number]',
		STRING_CLASS = '[object String]',
		ARRAY_CLASS = '[object Array]',
		DATE_CLASS = '[object Date]',
		CODAGENIC_O_CLASS = '[object Codagenic.O]',
		NATIVE_JSON_STRINGIFY_SUPPORT = window.JSON &&
			typeof JSON.stringify === 'function' &&
			JSON.stringify(0) === '0',

		// Some versions of JScript fail to enumerate over properties, names of which 
		// correspond to non-enumerable properties in the prototype chain
		DONT_ENUMS = ['toString', 'toLocaleString', 'valueOf', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'constructor'],
		IS_DONTENUM_BUGGY = (function ()
		{
			for (var p in { toString: 1 })
			{
				// check actual property name, so that it works with augmented prototype
				if (p === 'toString') {return false;}
			}
			return true;
		})();

	// Version refs
	Codagenic.version = $.codagenic.version = VERSION;

	// Application Namespaces
	Codagenic.Models = {};
	Codagenic.Views = {};
	Codagenic.Controllers = {};


	// =====================================================
	// Codagenic jQuery effn Class
	// =====================================================
	// Add the codagenic plugin to the jQuery.fn object
	// this is basically a constructor wrapper for all the different codagenic modules.
	// The parameter "action" is expecting a string of an internal module function name.
	// It is to be prefixed with the word 'set' for easy redability, 
	// eg "setControls" would be 
	//
	// $("#element").codagenic("setControls", {});
	//
	// TODO: build in a traditional callback

	$.fn.codagenic = function(action, options)
	{
			if($.isEmptyObject(options))
			{
				options = {};
			}

			var obj = this,
				_constructor = "",
				_runInitiator = function(constructor)
				{
					// iterate through the DOM elements we are attaching the plugin to
					return obj.each(function()
					{
						// attach new module
						return new Codagenic[constructor](this, options);
					});
				};

			//Constructor house keeping
			if(typeof action !== "string")
			{
				throw new Error(".codagenic(action, options); Unexpected 'action' ILLEGAL");
			}

			if(action.search("set") === -1 || action.length < 3)
			{
				throw new Error(".codagenic(action, options); Unxepected 'action' (" + action + "). Did you prefix with \"set\"?");
			}

			// Remove the "set" prefix
			// eg "setControls" becomes "Controls"
			_constructor = action.substring(3, action.length);

			// Check if codagenic module is installed/available
			if(!$.isFunction(Codagenic[_constructor]))
			{
				// No module found yet

				//Lets try lowercase first letter
				_constructor = _constructor.substring(0,1).toLowerCase() + _constructor.substring(1, _constructor.length);
				if($.isFunction(Codagenic[_constructor]))
				{
					return _runInitiator(_constructor);
				}

				// Lets try lower case then
				_constructor = _constructor.toLowerCase();
				if($.isFunction(Codagenic[_constructor]))
				{
					return _runInitiator(_constructor);
				}

				// Bummer lets try upper case
				_constructor = _constructor.toUpperCase();
				if($.isFunction(Codagenic[_constructor]))
				{
					return _runInitiator(_constructor);
				}

				//Exhasted so throw
				throw new Error(".codagenic(action, options); Module (" + _constructor + ") not available.");
			}

			return _runInitiator(_constructor);
	};



	/*
	*  Cross-broswer add properties
	*
	*/
	Codagenic.addProperty = function (obj, name, settings)
	{

		// wrapper functions
		var
			oldValue = obj[name],
			getFn = function ()
			{
				return settings.get.apply(obj, [oldValue]);
			},
			setFn = function (newValue)
			{
				oldValue = settings.set.apply(obj, [newValue]);
				return oldValue;
			};

		try
		{
			// Modern browsers, IE9+, and IE8 (must be a DOM object),
			Object.defineProperty(obj, name, {
				get: getFn,
				set: setFn
			});

			return;
		}
		catch (e)
		{
		}

		try
		{
			// Older Mozilla
			obj.__defineGetter__(name, getFn);
			obj.__defineSetter__(name, setFn);

			return;
		}
		catch (e)
		{
		}


		// IE6-7
		// must be a real DOM object (to have attachEvent) and must be attached to document (for onpropertychange to fire)
		var onPropertyChange = function (e)
		{

			if (event.propertyName === name)
			{
				// temporarily remove the event so it doesn't fire again and create a loop
				obj.detachEvent("onpropertychange", onPropertyChange);

				// get the changed value, run it through the set function
				var newValue = setFn(obj[name]);

				// restore the get function
				obj[name] = getFn;
				obj[name].toString = getFn;

				// restore the event
				obj.attachEvent("onpropertychange", onPropertyChange);
			}
		};

		obj[name] = getFn;
		obj[name].toString = getFn;

		obj.attachEvent("onpropertychange", onPropertyChange);

	};
;/* =============================================================
 * codagenicJS Library
 * http://www.codagenic.com/
 * =============================================================
 * Copyright 2012 Codagenic, All Rights Reserved.
 *
 * Created by Byron Salau
 * ============================================================ */


/**
*   Get Objects type  
*
*   @param   object   obj         the object to test
*   @returns string               returns the objects type
*/
Codagenic.type = function (obj)
{

    if (obj === null)
        {return NULL_TYPE;}

    switch (typeof obj)
    {
        case 'boolean': return BOOLEAN_TYPE;
        case 'number': return NUMBER_TYPE;
        case 'string': return STRING_TYPE;
        case 'undefined': return UNDEFINED_TYPE;
        case 'error': return ERROR_TYPE;
        case 'function': return FUNCTION_TYPE;
        default: return OBJECT_TYPE;
    }
};

/**
*   Test if jQuery   
*
*   @param   object   obj         the object to test
*   @returns bool                 returns 'true' if object is instance of jQuery
*/
Codagenic.isJquery = function (obj)
{
    return obj instanceof $;
};

/**
*   Test if null
*
*   @param   object   obj         the object to test
*   @returns bool                 returns 'true' if object is null
*/
Codagenic.isNull = function (obj)
{
    return obj === null;
};

/**
*   Test if DOM Element
*
*   @param   object   obj         the object to test
*   @returns bool                 returns 'true' if object is an element
*/
Codagenic.isElement = function (obj)
{
    return !!(obj && obj.nodeType === 1);
};

/**
*   Test if string
*
*   @param   object   obj         the object to test
*   @returns bool                 returns 'true' if object is a string
*/
Codagenic.isString = function (obj)
{
    return Codagenic.type(obj) === STRING_TYPE;
};

/**
*   Test if object
*
*   @param   object   obj         the object to test
*   @returns bool                 returns 'true' if object is of object type
*/
Codagenic.isObject = function (obj)
{
    return obj !== null && Codagenic.type(obj) === OBJECT_TYPE;
};

/**
*   Test if Array
*
*   @param   object   obj         the object to test
*   @returns bool                 returns 'true' if object is Array
*/
Codagenic.isArray = function (obj)
{
    return obj !== null && Codagenic.type(obj) === OBJECT_TYPE && 'splice' in obj && 'join' in obj;
};

/**
*   Test if function
*
*   @param   object   obj         the object to test
*   @returns bool                 returns 'true' if object is a function
*/
Codagenic.isFunction = function (obj)
{
    return (Object.prototype.toString.call(obj).indexOf(FUNCTION_TYPE) > -1);
};


/**
*   Test if number
*
*   @param   object   obj         the object to test
*   @returns bool                 returns 'true' if object is a number
*/
Codagenic.isNumber = function (obj)
{
	return !isNaN(parseFloat(obj)) && isFinite(obj);
};


/**
*   Test if boolean
*
*   @param   object   obj         the object to test
*   @returns bool                 returns 'true' if object is a boolean
*/
Codagenic.isBoolean = function (obj)
{
    return Codagenic.type(obj) === BOOLEAN_TYPE;
};

/**
*   Test if date
*
*   @param   object   obj         the object to test
*   @returns bool                 returns 'true' if object is a date
*/
Codagenic.isDate = function (obj)
{
    return obj instanceof Date;
};


/**
*   Test if defined
*
*   @param   object   obj         the object to test
*   @returns bool                 returns 'true' if object is defined
*/
Codagenic.isDefined = function (obj)
{
    return Codagenic.type(obj) !== UNDEFINED_TYPE;
};

/**
*   Test if error
*
*   @param   object   obj         the object to test
*   @returns bool                 returns 'true' if object is error object
*/
Codagenic.isError = function (obj)
{
    return Codagenic.type(obj) === ERROR_TYPE;
};


Codagenic.isSet = function (obj)
{
	/// <summary>Tests if object set</summary>
	/// <param name="obj" type="Object">The object to test</param>
	/// <returns type="bool">true if the object is determined to be set and not empty</returns>

    var o, self = this;

    if (!self.isDefined(obj))
        {return false;}

    if (self.isString(obj))
        {return obj !== '';}

    if (self.isArray(obj))
        {return obj.length > 0;}

    if (self.isNumber(obj))
        {return obj > 0;}

    if (self.isDate(obj) || self.isFunction(obj))
        {return true;}

    if (self.isError(obj))
        {return obj.message.length > 0;}

    if (self.isObject(obj))
    {
        for (var prop in obj)
        {
            if (obj.hasOwnProperty(prop))
                {return true;}
        }

        return false;
    }

    throw "Unable to determine if '" + obj + "' set or empty";
};


Codagenic.isEqual = function (a, b, sortArrays)
{
	/// <summary>
	/// Test isEqual   
	/// Determines whether two objects are "identical" or not, meaning they have the same (===) values and/or property values.
	/// In order for to return true with no sortArrays set to true, a and b arguments must both be one of three three following:
	/// a) a `string`, `boolean`, or 'number` type, and `a === b`
	/// b) an `array` object, and have the same element values in the same order
	/// c) an `object` type, and a), b), and./or c) are true for all its properties values, irrespective of property name order
	/// </summary>
	/// <param name="a" type="Object">An Object</param>
	/// <param name="b" type="Object">An Object to test</param>
	/// <returns type="bool">returns 'True' if the objects are determined to be the same</returns>


	function sort(object)
	{
		if (sortArrays === true && Array.isArray(object))
		{
			return object.sort();
		}
		else if (typeof object !== "object" || object === null)
		{
			return object;
		}

		return Object.keys(object).sort().map(function (key)
		{
			return {
				key: key,
				value: sort(object[key])
			};
		});
	}

	return JSON.stringify(sort(a)) === JSON.stringify(sort(b));
};

/**
*   Deep copy object
*
*   @param   object   obj         the object to copy
*   @returns object               returns a deep copy of the orginal object
*/
Codagenic.copy = function (obj)
{
    var out, i;

    if (Object.prototype.toString.call(obj) === ARRAY_CLASS)
    {
        var len = obj.length; out = []; i = 0;
        for (; i < len; i++)
        {
            out[i] = Codagenic.copy(obj[i]);
        }
        return out;
    }
    if (typeof obj === 'object')
    {
        out = {};
        for (i in obj)
        {
            out[i] = Codagenic.copy(obj[i]);
        }
        return out;
    }

    return obj;
};

/**
*   Unique ID
*
*   @returns string		
*/
Codagenic.UniqueId = function ()
{
	// always start with a letter (for DOM friendlyness)
	var idstr = String.fromCharCode(Math.floor((Math.random() * 25) + 65));
	do
	{
		// between numbers and characters (48 is 0 and 90 is Z (42-48 = 90)
		var ascicode = Math.floor((Math.random() * 42) + 48);
		if (ascicode < 58 || ascicode > 64)
		{
			// exclude all chars between : (58) and @ (64)
			idstr += String.fromCharCode(ascicode);
		}
	} while (idstr.length < 32);

	return ('CG_' + idstr);
};
;/* =============================================================
* codagenicJS Library
* http://www.codagenic.com/
* =============================================================
* Copyright 2012 Codagenic, All Rights Reserved.
*
* Created by Byron Salau
* ============================================================ */



/**
*  Creates an Codagenic.Array from an Array like object.
*
*  @param   Array     arr     The native array object 
*
*  @returns Codagenic.Array
*/
Codagenic.A = function (arr)
{
    if (this instanceof Codagenic.A)
    {
        if (Codagenic.isArray(arr))
        {
            for (var i = 0; i < arr.length; i++)
            {
                this.push(arr[i]);
            }
        }
        else if (Codagenic.isSet(arr))
        {
            this.push(arr);
        }

        return this;
    }

    return new Codagenic.A(arr);

};

/*
*    Extend Native Array
*/
Codagenic.A.prototype = [];


/*
*   Re-order Array containting numbers
* 
*   @param   string     dir     Expects 'ASEC' for ascending (default) or 'DESC' for decending
*/
Codagenic.A.prototype.numSort = function (dir)
{
    if(Codagenic.isString(dir))
    {
        if(dir.toUpperCase() !== 'ASEC' && dir.toUpperCase() !== 'DESC')
            {return undefined;}
    }
    if (dir === 'DESC')
    {
        this.sort(function (a, b) { return a + b; });
    }
    else
    {
        this.sort(function (a, b) { return a - b; });
    }
    return this;
};


/*
*  Itterates over the array items
*
*  @param function      e    the method to handle each callback with context
*/
Codagenic.A.prototype.each = function (e)
{
    var i = 0,
        l = this.length;

    for ( ; i < l; i++)
    {
        if (e.apply(this[i], [i]) === false)
        {
            break;
        }
    }

    return this;
};


/*
*  Find an object in array
*
*  @param function      func    the iterator
*
*  returns the found object or null if not found
*/
Codagenic.A.prototype.find = function (func)
{
    for (var i = 0, l = this.length; i < l; ++i)
    {
        var item = this[i];
        if (func(item))
            {return item;}
    }
    return null;
};

/*
*  object exists in array
*
*  @param object      sender    the object
*
*  returns boolean if found object
*/
Codagenic.A.prototype.has = function (sender)
{
	var r = this.find(function (item)
	{
		return item === sender;
	});

	if (r === null)
	{
		return false;
	}

	return true;
};


/*
*  Find object(s) in array
*
*  @param function      func    the iterator
*
*  returns an array of found objects
*/
Codagenic.A.prototype.where = function (func)
{
    var found = Codagenic.A();
    for (var i = 0, l = this.length; i < l; ++i)
    {
        var item = this[i];
        if (func(item))
            {found.push(item);}
    }
    return found;
};

/*
*  First object in array
*
*  returns the object
*/
Codagenic.A.prototype.first = function ()
{
    return this[0];
};

/*
*  Last object in array
*
*  returns the object
*/
Codagenic.A.prototype.last = function ()
{
    return this[this.length - 1];
};

/*
*  Convert to native array
*
*  returns native array
*/
Codagenic.A.prototype.toNative = function ()
{
    var items = [];
    for (var i = 0, l = this.length; i < l; ++i)
    {
        items.push(this[i]);
    }
    return items;
};

/*
*  Clone array
*
*  returns cloned array
*/
Codagenic.A.prototype.clone = function ()
{
    return Codagenic.A(this);
};

Codagenic.A.prototype.toString = function ()
{
	return this.join();
};
;/* =============================================================
* codagenicJS Library
* http://www.codagenic.com/
* =============================================================
* Copyright 2012 Codagenic, All Rights Reserved.
*
* Created by Byron Salau
* ============================================================ */


/**
*  Creates an Codagenic.Object from an Object like object.
*
*  @param   Object     obj     The native object (optional)
*
*  @returns Codagenic.Object
*/
Codagenic.O = function (obj)
{
    if (this instanceof Codagenic.O)
    {
        if (Codagenic.isObject(obj))
        {
            for (var p in obj)
            {
                if (obj.hasOwnProperty(p)) {this[p] = obj[p];}
            }
        }

        return this;
    }


    return new Codagenic.O(obj);

};

/*
*    Extend Native Object
*/
Codagenic.O.prototype = {};


/*
*    Broadcast class
*/
Codagenic.O.prototype.toString = function ()
{
    return CODAGENIC_O_CLASS;
};


/*
*  Itterates over the objects properties
*
*  @param function      e               the method to handle each callback with context
*  @param boolean       incPrototypes   Include the objects prototypes in the loop
*/
Codagenic.O.prototype.each = function (e)
{
    var key,
        i = 0;
    for (key in this)
    {
        if (this.hasOwnProperty(key))
        {
            if (e.apply(this[key], [i]) === false)
            {
                break;
            }
            i++;
        }
    }

    return this;
};


/*
*  Clone
*
*  returns the cloned object
*
*/
Codagenic.O.prototype.clone = function ()
{
    return Codagenic.copy(this);
};

/*
*  Test if Object is an Array
*
*  @returns bool     returns 'true' if object is Array   
*/
Codagenic.O.prototype.isArray = function ()
{
    return Codagenic.isArray(this);
};

/*
*  Test if Object is Function
*
*  @returns bool     returns 'true' if object is Function   
*/
Codagenic.O.prototype.isFunction = function ()
{
    return Codagenic.isFunction(this);
};

/*
*  Test if Object is Number
*
*  @returns bool     returns 'true' if object is Number   
*/
Codagenic.O.prototype.isNumber = function ()
{
    return Codagenic.isNumber(this);
};


/*
*  Test if Object is String
*
*  @returns bool     returns 'true' if object is String   
*/
Codagenic.O.prototype.isString = function ()
{
    return Codagenic.isString(this);
};

/*
*  Test if Object is Date
*
*  @returns bool     returns 'true' if object is Date   
*/
Codagenic.O.prototype.isDate = function ()
{
    return Codagenic.isDate(this);
};

/*
*  Test if Object is eqaul
*
*  @returns bool     returns 'true' if object is equal   
*/
Codagenic.O.prototype.equals = function (x)
{
    var p;
    for (p in this)
    {
        if (typeof (x[p]) === 'undefined') { return false; }
    }

    for (p in this)
    {
        if (this[p])
        {
            switch (typeof (this[p]))
            {
                case 'object':
                    if (!this[p].equals(x[p])) { return false; } break;
                case 'function':
                    if (typeof (x[p]) === 'undefined' ||
                        (p !== 'equals' && this[p].toString() !== x[p].toString()))
                        {return false;}
                    break;
                default:
                    if (this[p] !== x[p]) { return false; }
            }
        } else
        {
            if (x[p])
                {return false;}
        }
    }

    for (p in x)
    {
        if (typeof (this[p]) === 'undefined') { return false; }
    }

    return true;
};
;/* =============================================================
* codagenicJS Library
* http://www.codagenic.com/
* =============================================================
* Copyright 2012 Codagenic, All Rights Reserved.
*
* Created by Byron Salau
* ============================================================ */

/**
*  Encapsulate the native string inside an object
*
*  @param   Object     obj     The string to wrap
*
*  @returns String
*/
Codagenic.S = function (obj)
{
    var _string = new String(obj);

    _string.constructor = Codagenic.S;

    for (var i in Codagenic.S.prototype)
    {
        if (!Object.prototype[i])
        {
            _string[i] = Codagenic.S.prototype[i];
        }
    }

    return _string;
};

/*
*  Truncates a string to given `length` and appends `suffix` to it
*
*  @param Number      length    (optional)  the length of MAX characters to return
*  @param String      suffix    (optional)  the string to append indicate it has been truncated.
*
*  @returns String
*/
Codagenic.S.prototype.truncate = function (length, suffix)
{
    length = length || 30;
    suffix = Codagenic.isDefined(suffix) && Codagenic.isString(suffix) ? suffix : '...';
    return this.length > length ? this.slice(0, length - suffix.length) + suffix : String(this);
};


/*
*  Strips a string of any HTML tags.
*
*  Note that will only strip HTML 4.01 tags - like
*  `div`, `span`, and `abbr`. It will not strip namespace-prefixed tags
*  such as `h:table` or `xsl:template`.
*
*  @returns String          returns left, and retains right
*/
Codagenic.S.prototype.stripTags = function ()
{
    return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, '');
};


/*
*  Email Check
*
*  @returns Boolean     true if the value is an email address
*/
Codagenic.S.prototype.isValidEmailAddress = function ()
{
    var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    return re.test(this);
};

/*
*  Value/Length Check
*
*  @returns Boolean     true if string is not empty
*/
Codagenic.S.prototype.isNullOrEmpty = function ()
{
    return this.length > 0;
};

/*
*  Starts With
*
*  @param   String      prefix     the string to test
*
*  @returns Boolean                returns true if string starts with prefix
*/
Codagenic.S.prototype.startsWith = function (prefix)
{
    return (this.indexOf(prefix) === 0);
};

/*
*  Ends With
*
*  @param   String      suffix     the string to test
*
*  @returns Boolean                 returns true if string ends with suffix
*/
Codagenic.S.prototype.endsWith = function (suffix)
{
    return this.indexOf(suffix, this.length - suffix.length) !== -1;
};




/*
*  Append String
*
*  @param   String      str     the string to append
*
*  @returns String              returns left, and retains right
*/
Codagenic.S.prototype.appendString = function (str)
{
    return Codagenic.S(this + str);
};

/*
*  Prepend String
*
*  @param   String      str     the string to prepend
*
*  @returns String              returns left, and retains right
*/
Codagenic.S.prototype.prependString = function (str)
{
    return Codagenic.S(str + this);
};


/*
*  Set URL Param
*  (eg ?key=value)
*
*  @param   String      key     the parameter key
*  @param   String      value   the parameter value
*
*  @returns String              returns left, and retains right
*/
Codagenic.S.prototype.setUrlParam = function (key, value)
{
    var re = new RegExp("([?|&])" + key + "=.*?(&|$)", "i"),
    separator = this.indexOf('?') !== -1 ? "&" : "?";
    if (this.match(re))
    {
        return Codagenic.S(re, '$1' + key + "=" + value + '$2');
    }
    else
    {
        return Codagenic.S(this + separator + key + "=" + value);
    }
};
;/* =============================================================
 * codagenicJS Library
 * http://www.codagenic.com/
 * =============================================================
 * Copyright 2012 Codagenic, All Rights Reserved.
 *
 * Created by Byron Salau
 * ============================================================ */
;/* =============================================================
* codagenicJS Library
* http://www.codagenic.com/
* =============================================================
* Copyright 2012 Codagenic, All Rights Reserved.
*
* Created by Byron Salau
* ============================================================ */



/**
* API
* @namespace
*/
Codagenic.API = {};


/**
*  Client Default properties
*/
Codagenic.API.defaults =
{
	baseURI:        '/Store/Services',
	productPath:    'Products.ashx',
	assetPath:      'Assets.ashx',
	ordersPath:     'Orders.ashx',
	wishlistPath:   'Wishlist.ashx',
	async:          true,
	cache:          true,
	pushHistory:    false
};


Codagenic.API._buildURI = function (requestURI, params)
{
	var filteredParams = {},
		reservedKeys = Codagenic.A(["async", "cache", "baseURI", "success", "failure", "timeout", "pushHistory", "encode", "productPath", "assetPath", "ordersPath", "wishlistPath", "error"]);

	//Determin 'type' of get request
	switch (params.type)
	{
		case "product":
		case "category":
			requestURI += params.productPath + "?";
			break;
		case "order":
			requestURI += params.ordersPath + "?";
			break;
		case "wishlist":
			requestURI += params.wishlistPath + "?";
			break;
		default:
			throwAPIError("Request type of '" + params.type + "' is unkown.");
			break;
	}

	for (var key in params)
	{
		if (!reservedKeys.has(key))
		{
			filteredParams[key] = params[key];
		}
	}

	return requestURI + $.param(filteredParams);
};

/*
* MAIN FETCH METHOD
*/
Codagenic.API.get = function( settings, callback )
{

	// To avoid scope issues, we use 'base' instead of 'this'
	// to reference this class from internal events and functions.
	var base = this;

	base.init = function()
	{

		//Validate settings
		validate( settings );

		// Extend the defaults
		base.options = $.extend({}, Codagenic.API.defaults, settings);

		// Insure last character in URI is a "/""
		var request = base.options.baseURI;
		if (request[request.length - 1] !== "/")
		{
			request += "/";
		}

		//Build the URL
		request = base._buildURI(request, base.options);

		// Make the underlying ajax request
		$.ajax(
		{
			url: request,
			dataType: 'json',
			async: base.options.async,
			cache: base.options.cache,
			success: function(data)
			{
				// Success callback(s)
				if($.isFunction( callback ))
				{
					callback(data);
				}

				// Successful return but lets determine the "status" from the service
				// and handle accordionly
				if(typeof data.status !== "undefined")
				{
					// Everything went swimingly, lets pass back
					if(data.status === "success" && $.isFunction( settings.success ))
					{
						settings.success( data.data );
					}

					// Service has returned failure even though the ajax request was successful
					else if (data.status === "failure" && $.isFunction( settings.failure ))
					{
						settings.failure(data);
					}
				}
			},
			failure: function(data)
			{
				// Failure callback(s)
				// These are native underlying jQuery failures
				if($.isFunction( callback ))
				{
					callback(data);
				}

				if($.isFunction( settings.failure ))
				{
					settings.failure(data);
				}
			}
		});


	};

		// *PRIVATE* Validate passing in settings
		var validate = function( params )
		{
			// Dont do a request if nothing is being requested
			if (typeof settings !== "object")
			{
				throwAPIError("'params' was not defined or is not an object");
			}

			// Dont do a request if type is not specified
			if ( typeof settings.type === "undefined")
			{
				throwAPIError("'type' was not defined");
			}
		};

		// *PRIVATE* Method for safe error handling 
		var throwAPIError = function( reason )
		{
			if ( $.isFunction( callback ) )
			{
				callback( {status:"failure", type: reason } );
			}

			throw new Error( reason );
		};

		// Run constructor
		base.init();

		return this;
	};

	/*
	*  RETURNS: bool if the variation has an attribute that matches a key value pair
	*  PARAM: title *string - the title of the attribute to match
	*  PARAM: value *string - the value of the attribute to match
	*/
	Codagenic.API.post = function (settings, callback)
	{
		// To avoid scope issues, we use 'base' instead of 'this'
		// to reference this class from internal events and functions.
		var base = this;


		base.init = function ()
		{
			// Extend the defaults
			base.options = $.extend(Codagenic.API.defaults, settings);

			// Insure last character in URI is a "/""
			var request = base.options.baseURI;
			if (request[request.length - 1] !== "/")
			{
				request += "/";
			}

			var successHandler = function (data)
			{
				if (Codagenic.isFunction(base.options.success))
				{
					base.options.success(data);
				}

				if (Codagenic.isFunction(callback))
				{
					callback(data);
				}
			},

			errorHandler = function (jqXHR, textStatus, errorThrown)
			{
				if (Codagenic.isFunction(base.options.error))
				{
					base.options.error(jqXHR, textStatus, errorThrown);
				}
			};

			//Build the URL
			request = base._buildURI(request, base.options);

			// Make the underlying ajax request
			$.ajax({
				url: request,
				type: 'POST',
				async: base.options.async,
				cache: base.options.cache,
				data: base.options.data,
				success: successHandler,
				error: errorHandler
			});
		};

		base.init();
	};
;/*
*  api.product.js
*  CodagenicJS
*
*  Created by Byron Salau
*  Copyright 2012 Codagenic. All rights reserved.
*/


/*
*  PRODUCT MODEL
*/
Codagenic.Models.product = function (data)
{
	this.id = data.id;
	this.defaultAsset = data.defaultAsset;
	this.title = data.title;
	this.url = data.url;
	this.variations = Codagenic.A();
	this.video = data.video;
	this.images = Codagenic.A(data.images);
	this.videos = Codagenic.A(data.videos);
	this.documents = Codagenic.A(data.documents);

	for (var i = 0; i < data.variations.length; i++)
	{
		this.variations.push(new Codagenic.Models.variation(data.variations[i]));
	}

	return this;
};

/*
*  ATTRIBUTE MODEL
*/
Codagenic.Models.attribute = function (obj)
{
	this.title =        obj.title;
	this.selectable =   obj.selectable;
	this.value =        obj.value;
	this.variation =	obj.variation;
	//Private 
	//var _variation = obj.variation;


	//Codagenic.addProperty(this, "variation", {
	//	get: function () { return _variation instanceof Codagenic.model.variation ? _variation : null; },
	//	set: function () { return null; } //Not Allowed
	//});

	//Object.defineProperty(this, "variation", {
	//    get: function () { return _variation instanceof Codagenic.model.variation ? _variation : null; },
	//    set: function () { return null; }, //Not Allowed
	//    enumerable: false,
	//    configurable: false
	//});

	return this;
};


/*
*  ATTRIBUTES MODEL
*/
Codagenic.Models.attributes = function (obj)
{

	// Public
	this.title = obj ? obj.title || null : null;
	this.values = obj ? Codagenic.A(obj.values) || Codagenic.A() : Codagenic.A();

	return this;
};



/*
*  VARIATIONS MODEL
*/
Codagenic.Models.variation = function (obj)
{
	// Variation with object
	if (typeof obj !== 'undefined')
	{
		this.id = obj.id;
		this.attributes = Codagenic.A(obj.attributes);
		this.code = obj.code;
		this.defaultPrice = Codagenic.O(obj.defaultPrice);
		this.currentPrice = Codagenic.O(obj.currentPrice);
		this.qty = obj.quantity;
		this.sku = obj.sku;
		this.swatchImage = obj.swatchImage;
		this.swatchCode = obj.swatchColourCode;
		this.values = Codagenic.A();
		this.isDefault = obj.defaultVariation;
		this.images = Codagenic.A(obj.images);
		this.videos = Codagenic.A(obj.videos);
		this.documents = Codagenic.A(obj.documents);
	}

	return this;
};


/*
*  RETURNS: bool if the variation has an attribute that matches a key value pair
*  PARAM: title *string - the title of the attribute to match
*  PARAM: value *string - the value of the attribute to match
*/
Codagenic.Models.variation.prototype.hasAttribute = function (title, value)
{
	var result = false;
	this.attributes.each(function ()
	{
		if (this.title === title && this.value === value) { result = true; return false; }
	});
	return result;
};

/*
*  RETURNS: bool if the variation has stock available
*/
Codagenic.Models.variation.prototype.hasStock = function ()
{
	return this.qty > 0;
};

/*
*  RETURNS: bool if the variation has images
*/
Codagenic.Models.variation.prototype.hasImages = function ()
{
	return Codagenic.isSet(this.images);
};

/*
*  RETURNS: bool if the variation has documents
*/
Codagenic.Models.variation.prototype.hasDocuments = function ()
{
	return Codagenic.isSet(this.documents);
};

/*
*  RETURNS: bool if the variation has videos
*/
Codagenic.Models.variation.prototype.hasVideos = function ()
{
	return Codagenic.isSet(this.videos);
};

/*
*  RETURNS: bool if the variation has assets
*/
Codagenic.Models.variation.prototype.hasAssets = function ()
{
	if(this.hasImages()){ return true; }
	if(this.hasDocuments()){ return true; }
	if(this.hasVideos()){ return true; }
	return false;
};

/*
*  RETURNS: an object of the variation assets
*/
Codagenic.Models.variation.prototype.getAssets = function ()
{
	var assets = Codagenic.O();
	assets.images = this.images;
	assets.documents = this.documents;
	assets.videos = this.videos;

	return assets;
};


/*
*  RETURNS: bool if the product has images
*/
Codagenic.Models.product.prototype.hasImages = function ()
{
	return Codagenic.isSet(this.images);
};

/*
*  RETURNS: bool if the product has documents
*/
Codagenic.Models.product.prototype.hasDocuments = function ()
{
	return Codagenic.isSet(this.documents);
};

/*
*  RETURNS: bool if the product has videos
*/
Codagenic.Models.product.prototype.hasVideos = function ()
{
	return Codagenic.isSet(this.videos);
};

/*
*  RETURNS: bool if the product has assets
*/
Codagenic.Models.product.prototype.hasAssets = function ()
{
	if(this.hasImages()){ return true; }
	if(this.hasDocuments()){ return true; }
	if(this.hasVideos()){ return true; }
	return false;
};

/*
*  RETURNS: an object of the product assets
*/
Codagenic.Models.product.prototype.getAssets = function ()
{
	var assets = Codagenic.O();
	assets.images = this.images;
	assets.documents = this.documents;
	assets.videos = this.videos;

	return assets;
};


/*
*  SUMMARY: Returns the products default variation
*/
Codagenic.Models.product.prototype.defaultVariation = function ()
{
	var v = null;

	this.variations.each(function ()
	{
		if (this.isDefault)
		{
			v = this;
			return false; // Breaks each loop
		}
	});

	if (!v && this.variations.length)
	{
		v = this.variations[0];
	}

	return v;
};

/*
*  PARAM: attributesToMatch *array - An Array of objects with title and value property pairs
*  RETURNS: Matched variation or null if not found
*/
Codagenic.Models.product.prototype.findVariationWithAttributes = function (attributesToMatch)
{
	if (!Codagenic.isDefined(attributesToMatch))
	{
		return null;
	}

	var v = null;

	this.variations.each(function ()
	{
		var matchCount = 0;

		// loop through attributes and check that we have a match
		for (var i = 0; i < attributesToMatch.length; i++)
		{
			var attributeToMatch = attributesToMatch[i];

			this.attributes.each(function ()
			{
				if (this.title === attributeToMatch.title && this.value === attributeToMatch.value)
				{
					matchCount++;
				}
			});
		}

		if (matchCount === this.attributes.length)
		{
			v = this; // this is our variation
			return false; // Breaks each loop
		}
	});

	return v;
};

/*
*  PARAM: title *string - the title of the attribute to match
*  PARAM: value *string - the value of the attribute to match
*  PARAM: hasStock *bool (optional) - if true only returns matches that have stock available
*  RETURNS: Variations that match a variation key value pair
*/

/*
*  Itterates all variations and finds all unique attributes
*
*  @param selectableOnly Boolean, if true only includes attributes that have selectable set to true 
*  @returns Codagenic.Array of the Attributes
*/
Codagenic.Models.product.prototype.listVariationsWithAttribute = function (title, value, hasStock)
{
	var list = Codagenic.A();
	this.variations.each(function ()
	{
		if (this.hasAttribute(title, value))
		{
			if (typeof hasStock === "boolean")
			{
				if (this.hasStock()) { list.push(this); }
			}
			else if (typeof hasStock === "undefined")
			{
				list.push(this);
			}
		}
	});
	return list;
};

/*
*  Itterates all variations and finds all unique attributes
*
*  @param selectableOnly Boolean, if true only includes attributes that have selectable set to true 
*  @returns Codagenic.Array of the Attributes
*/
Codagenic.Models.product.prototype.listUniqueAttributes = function (selectableOnly)
{
	var self = this,
		items = Codagenic.A();

	if (typeof selectableOnly !== 'boolean')
	{
		selectableOnly = false; //Default
	}

	self.variations.each(function ()
	{
		var variation = this;

		this.attributes.each(function ()
		{
			if ((!selectableOnly) || (selectableOnly && this.selectable))
			{
				var attribute =
					new Codagenic.Models.attribute({
						title: this.title,
						value: this.value,
						selectable: this.selectable,
						variation: variation
					}),

					hasAttribute = false;

				items.each(function ()
				{
					// look for and add if found
					if (this.title === attribute.title)
					{
						this.push(attribute);
						hasAttribute = true;
						return false;
					}
				});

				if (!hasAttribute)
				{
					var a = new Codagenic.Models.attributes();
					a.title = attribute.title;
					a.push(attribute);
					items.push(a);
				}
			}
		});
	});

	return items;
};


/*
*  PARAM: attributesToMatch *array - An Array of objects with title and value property pairs
*  RETURNS: Matched variation or null if not found
*/
Codagenic.Models.product.prototype.listAssetsForVariation = function (v)
{
	var self = this,
		target = null;
	if (!Codagenic.isSet(v))
		{throw "Variation is not set or undefined";}

	if (!(v instanceof Codagenic.Models.variation))
		{throw v + " is not an instance of Codagenic.Models.Variation";}

	if (Codagenic.isSet(self.images) ||
		Codagenic.isSet(self.documents) ||
		Codagenic.isSet(self.videos))
	{
		target = self;
	}

	if (Codagenic.isSet(v.iamges) ||
		Codagenic.isSet(v.documents) ||
		Codagenic.isSet(v.videos))
	{
		target = v;
	}

	if (target)
	{
		var assets = Codagenic.O();
		assets.images = Codagenic.A(target.images);
		assets.documents = Codagenic.A(target.documents);
		assets.videos = Codagenic.A(target.videos);
		return assets;
	}

	if (Codagenic.isSet(this.defaultAsset))
		{return Codagenic.A(this.defaultAsset);}

	return Codagenic.A();

};


Codagenic.Models.attributes.prototype.push = function (attribute)
{
	for (var i = 0; i < this.values.length; i++)
	{
		if (this.values[i].value === attribute.value)
		{
			return; // Dont duplicate values
		}
	}

	this.values.push(attribute);
};


/*
*   Re-order values
* 
*   @param   Array     values     The Array of possible values in the desired order
*
*   returns the sorted attributes    (Unknonws appended to the front)
*/
Codagenic.Models.attributes.prototype.sortByValues = function (values)
{
	if (!Codagenic.isArray(values) && !Codagenic.isSet(values))
		{throw "Unexpected Object";}

	this.values.sort(function (a, b)
	{
		return (values.indexOf(a.value)) - (values.indexOf(b.value));
	});

	return this;
};



Codagenic.API.getProduct = function (settings, callback)
{
	var _success = settings.success,
		successHandler = function (data)
		{
			if ($.isFunction(_success))
			{
				_success(new Codagenic.Models.product(data.data));
			}
		};

	Codagenic.API.get($.extend({ type: 'product', success: successHandler }, settings), function (data)
	{
		if ($.isFunction(callback))
		{
			callback(new Codagenic.Models.product(data.data));
		}
	});

	return true;
};


Codagenic.API.addProductToCart = function (settings, callback)
{

	settings.type = "order";
	settings.data.type = 'add';

	Codagenic.API.post(settings, function (data)
	{
		if ( $.isFunction(callback) )
		{
			callback(data);
		}
	});

	return true;
};

Codagenic.API.addProductToWishlist = function (settings, callback)
{

	settings.type = "wishlist";
	settings.data.type = 'add';

	Codagenic.API.post(settings, function (data)
	{
		if ($.isFunction(callback))
		{
			callback(data);
		}
	});

	return true;
};
;
	// =====================================================
	// Category Class
	// =====================================================
	//
	// REQUIRED: API
	// USAGE:
	// TODO:
	Codagenic.category = function(element, options) {

		// @private
		//
		// To avoid scope issues, we use 'base' instead of 'this'
		// to reference this class from internal events and functions.
		var base = this,
			$element = $(element),      // reference to the jQuery version of DOM element

			defaults =                  // default options
			{
				type: "LoadMore",       // "LoadMore", "LazyLoad", "None"
				replaceHistroy: true,   // Uses window.replaceHistory API to retain ajaxed products
				lazyLoadOffset: 20,      // Trigger load 20px from bottom

				// Templates
				itemTemplate: function(item)
				{
					return "<li><a href=\"" + item.url + "\">" + item.title + "</a></li>";
				},
				loadBtnTemplate: function()
				{
					return "<a>Load More</a>";
				},

				// Callbacks
				onPagerLoad: null,
				onPagerFinish: null
			};


		// the "constructor" method that gets called when the object is created
		base.init = function() {

			// the plugin's final properties are the merged default and
			// user-provided options (if any)
			base.options = $.extend({}, defaults, options);

			if(base.options.type.toLowerCase() === "none")
			{
				return; // Exit
			}

			var $pager =        $('#pager'),
				_totalPages =	Codagenic.isDefined($element.data('total-pages')) ? parseInt($element.data('total-pages'), 10) : -1,
				_categoryId =   $element.data('category-id'),
				_pageSize =		Codagenic.isDefined($element.data('page-size')) ? parseInt($element.data('page-size'), 10) : 10,
				_page =         Codagenic.isDefined($element.data('page')) ? parseInt($element.data('page'), 10) + 1 : 0;

				if(_totalPages === "undefined" || _categoryId === "undefined")
				{
					throw new Error("Codagenic.category.init(), ERROR: 'data-total-pages' and/or 'data-category-id' attributes not specified" );
				}

				//Hide native
				$pager.hide();

				if(_totalPages > 1)
				{
					// Paging available

					var $btnLoadMore,
						$products = $element.find('ul.product-listing'),
						_successHandler = function(data){

								// Build new products HTML
								var html = "";
								for(var i = 0; i< data.products.length; i++ )
								{
									html += base.options.itemTemplate( data.products[i] );
								}

							// Inject new products into DOM
							$products.append(html);

							// Update history if available and configured
							if(window.history.replaceState && base.options.replaceHistroy)
							{
								window.history.replaceState($products.html(), window.document.title, window.document.URL);
							}
						},
						_failureHandler = function(data)
						{
							// TODO: Make a more usuable error
							throw new Error(data);
						},
						_load = function( _callback )
						{
							// Public callback
							if($.isFunction(base.options.onPagerLoad))
							{
								base.options.onPagerLoad({
									page: _page,
									pageSize: _pageSize,
									totalPages: _totalPages
								});
							}

							// Add loading class
							$('body').addClass('codagenic-loading');

							// Hit the API service
							Codagenic.API.getProductListing({
								id: _categoryId,
								pageSize: _pageSize,
								pageNumber: _page,
								success: _successHandler,
								failure: _failureHandler

							}, function(){ // API callback

								// Remove loading class
								$('body').removeClass('codagenic-loading');

								// Public callback
								if($.isFunction(base.options.onPagerFinish))
								{
									base.options.onPagerFinish({
										pageNumber: _page,
										pageSize: _pageSize,
										totalPages: _totalPages
									});
								}

								// Private callback
								if($.isFunction(_callback))
								{
									_callback();
								}

								// Increment Page Count
								_page++;

								// Validate page count
								if (_page >= _totalPages)
								{
									if(base.options.type.toLowerCase() === "loadmore")
									{
										// Remove load more button
										$btnLoadMore.remove();
									}
									else if(base.options.type.toLowerCase() === "lazyload")
									{
										//Remove the scroll event
										$(window).unbind("scroll.codagenic");
									}
								}
							});

						};

					if(base.options.type.toLowerCase() === "loadmore")
					{
						// Check if returned button is a template or an jquery object

						if(typeof base.options.loadBtnTemplate() === "string")
						{
							$btnLoadMore = $( base.options.loadBtnTemplate() );


							// Check if we have a pager control
							if($pager.length < 0)
							{
								$element.append( $btnLoadMore );
							}
							else
							{
								$pager.after( $btnLoadMore );
							}

							$btnLoadMore.wrap("<div class='load-more' />");
						}
						else if(typeof base.options.loadBtnTemplate() === "object")
						{
							//test its a jquery object
							if(base.options.loadBtnTemplate().jquery)
							{
								// it's (probably) a jQuery object
								$btnLoadMore = base.options.loadBtnTemplate();
							}
							else
							{
								// it's not
								throw new Error(".codagenic(\"setCategory\") Unexpected loadBtnTemplate");
							}
						}
						else
						{
							throw new Error(".codagenic(\"setCategory\") Unexpected loadBtnTemplate");
						}

						// Event handler
						$btnLoadMore.on("click", function(e)
						{
							// Load products
							_load();

							// Stop event propogation
							e.preventDefault();
						});

					}
					else if(base.options.type.toLowerCase() === "lazyload")
					{
						var _locked = false;

						// Set up scroll event listner
						$(window).bind("scroll.codagenic", function()
						{
							if($(window).scrollTop() + $(window).height() > $(window.document).height() - base.options.lazyLoadOffset)
							{
								_locked = true;
								_load(function()
								{
									_locked = false;
								});
							}
						});

					}


					// Handle history pop state if replace history configured
					if(base.options.replaceHistroy)
					{
						window.onpopstate = function(e)
						{
							if(window.history.state !== null)
							{
								$element.find('ul.product-listing').html( window.history.state );
							}
						};
					}

				}
		};

		base.init();
	};



	Codagenic.API.getProductListing = function (settings, callback)
	{

		settings.type = "category";

		Codagenic.API.get(settings, function ()
		{
			if (Codagenic.isFunction(callback))
			{
				callback();
			}
		});

		return true;
	};
;    // =====================================================
    // UI class
    // =====================================================
    Codagenic.UI = function( element, options )
    {
        // plugin's default options
        // this is private property and is  accessible only from inside the plugin
        var UI = this, // to avoid confusions, use "UI" to reference the current instance of the object
            defaults =
            {
                imgPreload: true,
                selectBox:
                {
                    remodal:    true,
                    type:       "checkboxes",
                    css:        "",
                    maxHeight:  400,
                    itemTemplate: function( item ){ return item.title; },

                    //callbacks
                    onSelect: null
                }
            };

        UI.init = function ()
        {

            // the plugin's final properties are the merged default and
            // user-provided options (if any)
            var _deep = true,
                _options = $.extend(_deep, {}, defaults, options);

            // iterate through the DOM elements
            $(element).each(function() {

                var _$element = $(this), // reference to the jQuery version of DOM element
                    node  = element.nodeName.toLowerCase();

                // if plugin has already been attached to the element
                // dont do anything
                if (_$element.hasClass('codagenic-ui-complete')) {return;}

                // Re-Modeller
                // Determind if DOM object is a single input or a DOM wrapper
                if( node === "select" ||
                    node === "input")
                {

                    if( _options.selectBox.remodal && node === "select" ){ UI.remodalSelect( _$element, _options.selectBox); }       // Select Box's
                }
                else
                {
                    // Not an input element
                    // Traversing required to find them within..

                    if(_options.selectBox.remodal){ UI.remodalSelect( _$element.find('select'), _options.selectBox ); }               // Select Box's

                }




            });

        };



        /*
         * Select Box / Drop Downs
         */
        UI.remodalSelect = function( element, options )
        {

            $(element).each(function(){

                var $obj = $(this);

                // Check has not already been replaced, if so dont do anything
                if( $obj.hasClass('codagenci-ui-replaced') ) { return; }

                // If its a multiple leave it alone
                // TODO: Create an awesome multiple handler
                if( $obj.attr('multiple') ) { return; }

                //Lets rock
                var $ui,
                    $ul = $('<ul />'),
                    _events = {};

                // Configure control for type
                if (options.type.toLowerCase() === "dropdown")
                {

                    var _$header = $('<div />');

                    $ui =   $('<div tabindex="0" class="codagenic-ui-dropdown" />').addClass(options.css).append($ul).prepend( _$header );

                    // Update header on item click event
                    _events.onClick = function( sender )
                    {
                        _$header.html( $(sender).text() ).trigger('click');
                    };

                    _events.onDOMInject = function ( sender )
                    {
                        // On inject add the selected text into the header
                        _$header.html( $(sender).find('option:selected').text() );

                        // Hide the dropdown
                        $ul.hide();
                    };


                    // Bind Show / hide dropdown event handler
                    _$header.on('click', function(event){

                        // If disabled dont do anything
                        if( $obj.attr('disabled') ){ return; }

                        // CSS
                        $(this).toggleClass('codagenic-ui-open');

                        if($ul.data('_visible'))
                        {

                            // Animate hide
                            $ul.animate({height:0}, {
                                queue: false,
                                duration: 300,
                                complete: function(){
                                    $(this).hide();
                                }
                            }).data('_visible',false);
                        }
                        else
                        {

                            // Begin Maths and trickery to make sure drop down fits on screen
                            // We calculate this everytime incase children have been added later
                            //
                            var _pageHeight = $(window.document).height();

                            // Needs to be in the dom to get metrics (Add it, but keep hidden)
                            $ul.css({'visibility':'hidden', 'height':'auto'}).show();

                            // Grab the metrics of the drop down while its hidden
                            var _top = $ul.offset().top,
                                _height = $ul.height();

                            // Reset it back ready for animation
                            $ul.hide().css({'visibility':'visible'});

                            // Set the max height if its needed
                            if(_height > options.maxHeight)
                            {
                                _height = options.maxHeight;
                                $ul.css({'overflow':'auto'});
                            }

                            // Check the dropdown will still fit on screen now
                            if ((_top + _height) > _pageHeight)
                            {
                                // the list is going outside the page
                                // Determin the space we have left on the window
                                // add 20px for some whitespace underneath
                                _height = _pageHeight - _top - 20;
                                $ul.css({'overflow':'auto'}).height( _height );
                            }

                            // Set the min width
                            $ul.css({'min-width': _$header.parent().width() });

                            // Animate in
                            $ul.height(0).show().animate({height: _height}, {queue: false, duration: 300}).data('_visible',true);

                            // Desktop Keyboard handler
                            $ui.focus().keydown(function(event)
                            {
                                // If key was "enter or return" then trigger the element that is in focus
                                if(event.keyCode === 13)
                                {
                                    $ui.find('li:focus').click();
                                    return;
                                }

                                // Loop each item to find a match, this could be slow if lots of items
                                $ul.children('li').each(function()
                                 {
                                    if ( $(this).data('_title')[0].toUpperCase() === String.fromCharCode( event.keyCode ).toUpperCase()  )
                                    {
                                        $(this).focus(); // Focus first element
                                        $ul.scrollTop( $(this).position().top - $ul.position().top ); //Scroll to element
                                        return false; //break loop
                                    }
                                 });
                            });

                        }


                        // Stop event bubbling as we are detecting clicks outside this element
                        event.stopPropagation();

                    });

                    // Detect Clicks Outside
                    // How am i doing this? go see:
                    // http://stackoverflow.com/questions/152975/how-to-detect-a-click-outside-an-element
                    $('body').on('click', function()
                    {
                        // If the list is open, lets close it up
                        if(_$header.hasClass('codagenic-ui-open'))
                        {
                            _$header.trigger('click');
                        }
                    });


                }
                else if (options.type.toLowerCase() === "checkboxes")
                {
                    // Create a pointer
                    $ui = $ul;

                    //CSS
                    $ul.addClass('codagenic-ui-checkboxes codagenic-ui-clear').addClass(options.css);
                }
                else
                {
                    // Unknown type setting
                    throw new Error("UI.remodalSelect( element, options ) Unknown type \"" + options.type + "\"");
                }


                // Itterate native options
                $obj.find('option').each(function(i)
                {
                    var _option = $(this),

                        // Values
                        _item =
                        {
                            index:      i,
                            title:      _option.text(),
                            value:      _option.val(),
                            selected:   _option.attr('selected') === 'selected' ? true : false //TODO: this is case sensitive need a work around
                        };

                        if(options.data !== "undefined")
                        {
                            _item.data = options.data;
                        }

                        // Build New Item
                        var _$li = $("<li tabindex=\"" + i + "\">" + options.itemTemplate( _item ) + "</li>").data({
                            "_value": _item.value,
                            "_title": _item.title
                        });

                    // CSS
                    if(_item.selected){ _$li.addClass('selected'); }

                    // Append Item
                    $ul.append( _$li );

                });

                if($.isFunction(_events.onDOMInject))
                {
                    //Pass through the native control as best practice
                    _events.onDOMInject($obj.get(0));
                }

                // Out with the old, In with the new
                $obj.wrap('<div class="codagenic-ui" />').hide().addClass('codagenci-ui-replaced').after($ui);


                if(options.type.toLowerCase() === "dropdown")
                {
                    // Set Width
                    $ui.width( $ul.width() + 32);

                    if(ISTOUCH)
                    {
                        // Native display for mobiles
                        $obj.css({'opacity':0, 'position':'absolute', 'left': 0, 'width': $ul.width() + 32 }).show();
                    }
                }



                // Event Delegate
                $ul.on("click", "li", function(e)
                {
                    // Update the native input
                    $obj.val( $(this).data("_value") ).trigger('change');

                    // CSS
                    $ul.find('li.selected').removeClass('selected');
                    $(this).addClass("selected");

                    // Callback(s)
                    if($.isFunction(options.onSelect))
                    {
                        options.onSelect(this, {
                            title: $(this).data("_title"),
                            value: $(this).data("_value")
                        });
                    }

                    if($.isFunction(_events.onClick))
                    {
                        _events.onClick( this );
                    }

                });


            });
        };


        //Run the constructor
        UI.init();

    };
;

    // =====================================================
    // TOGGLE PANEL
    // =====================================================
    //
    // REQUIRED: jQuery
    // USAGE:
    // TODO:
Codagenic.togglePanel = function (element, options)
{

    // @private
    //
    // To avoid scope issues, we use 'base' instead of 'this'
    // to reference this class from internal events and functions.
    var base = this,
            defaults =              // default options
            {
                panel: null,        // Target jquery element to show/hide
                toggleBtn: null,
                showOnInit: false,
                showBtn: null,
                hideBtn: null,
                animated: false,
                duration: 400,
                easing: "linear",

                onShow: F,
                onHide: F
            };

    // the "constructor" method that gets called when the object is created
    base.init = function ()
    {

        // If element passed in from jquery fn then set it as 
        // default before we extend options
        if (element)
        {
            defaults.panel = $(element);
        }

        // the plugin's final properties are the merged default and
        // user-provided options (if any)
        base.options = $.extend({}, defaults, options);

        // Dont do anything if no panel to show/hide or
        // Dont do anything if panel isnt a jquery object
        if (!base.options.panel || !Codagenic.isJquery(base.options.panel))
        {
            return;
        }

        // flag, used to detect if panel is on or off for toggle events
        var _open = false;

        // Detect if the panel is actually visible at the moment
        if (base.options.panel.is(':visible'))
        {
            // Panel is on/true/visible
            _open = true;
        }

        // Logic that shows the panel
        base.showPanel = function ()
        {
            if (base.options.animated)
            {
                base.options.panel.slideDown(base.options.duration, base.options.easing, function ()
                {
                    _open = true;
                    base.options.onShow(); // Callback
                });
            }
            else
            {
                _open = true;
                base.options.panel.show();
                base.options.onShow(); // Callback
            }
        };

        // logic that hides the panel
        base.hidePanel = function ()
        {
            if (base.options.animated)
            {
                base.options.panel.slideUp(base.options.duration, base.options.easing, function ()
                {
                    _open = false;
                    base.options.onHide();
                });
            }
            else
            {
                _open = false;
                base.options.panel.hide();
                base.options.onHide(); // Callback
            }
        };

        // Show button event handler
        if (Codagenic.isJquery(base.options.showBtn))
        {
            base.options.showBtn.on("click", function (e)
            {
                base.showPanel();
                e.preventDefault();
            });
        }

        // Hide button event handler
        if (Codagenic.isJquery(base.options.hideBtn))
        {
            base.options.hideBtn.on("click", function (e)
            {
                base.hidePanel();
                e.preventDefault();
            });
        }

        // Toggle button event handler
        if (Codagenic.isJquery(base.options.toggleBtn))
        {
            base.options.toggleBtn.on("click", function (e)
            {
                if (_open)
                {
                    base.hidePanel();
                }
                else
                {
                    base.showPanel();
                }

                e.preventDefault();
            });
        }

        // Panel Default
        if (base.options.showOnInit)
        {
            base.showPanel();
        }
        else
        {
            base.hidePanel();
        }
    };

    // call the "constructor" method
    base.init();

};

    //jQuery effin
    $.fn.togglePanel = function(options){
        return this.each(function () {
          if (!$.data(this, 'codagenic_togglePanel'))
          {
            $.data(this, 'codagenic_togglePanel', new Codagenic.togglePanel( this, options ));
          }
        });
    };
; 
	// =====================================================
	// Controls Class
	// =====================================================
	//
	// REQUIRED: UI
	// USAGE:
	// TODO:
	Codagenic.addToCart = function(element, options) {

		// @private
		//
		// To avoid scope issues, we use 'base' instead of 'this'
		// to reference this class from internal events and functions.
		var base = this,
			$element = $(element),  // reference to the jQuery version of DOM element

			defaults =              // default options
			{
				controls: {},
				defaultControl:
				{
					type: "dropdown",
					cssClass: "codagenic-control",
					itemTemplate: function( item ){ return item.title; },
					prepend: "",
					append: "",

					//callbacks
					onSelect: function(){}
				}
			};

		// constructor method
		base.init = function() {

			// the plugin's final properties are the merged default and
			// user-provided options (if any)
			base.options =  $.extend({}, defaults, options);
			base.id =       $element.data('id');


			$element.find('select').each(function(i){

				var _options = base.options.defaultControl,
					key = $(this).attr("title");

				// Check if developer has specified any custom settings.
				if(typeof base.options.controls[key] !== "undefined" && key !== "")
				{
					//developer has specific options
					_options = $.extend({}, base.options.defaultControl, base.options.controls[key]);
				}

				$(this).codagenic("setUI", {selectBox: _options} );

			});
		};

		// call the "constructor" method
		base.init();

	};
;  
  
	// phew!
	// Create a global reference to our library.
	global.Codagenic = Codagenic;

/*jsl:ignore*/
})(this, jQuery, window);
/*jsl:end*/